From 727df5268184fb42a14963002cc77c5c9cf5d692 Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Wed, 1 Dec 2021 17:17:40 -0700 Subject: [PATCH] retire cet (#777) * teach mkshort to split utf8 on grapheme boundaries. and retire some cet functions. * add the grapheme test. * wean gbfile from cet. * restore cet.cc, cet.h in prepare for deprecation. * retire cet.cc, cet.h * drop attempted support of \r line endings in gbfgetutf16str. * add test of surrogate pairs for gbfgetutf16str. * fix test --- CMakeLists.txt | 3 +- GPSBabel.pro | 3 +- defs.h | 1 + cet.cc => deprecated/cet.cc | 0 cet.h => deprecated/cet.h | 0 gbfile.cc | 112 ++++++++++++++++++---------- mkshort.cc | 30 ++++---- reference/grapheme.csv | 4 + reference/grapheme.gpx | 20 +++++ reference/testsupplementalplane.csv | 10 +++ reference/testsupplementalplane.pcx | Bin 0 -> 1986 bytes reference/track/nmea_utf16 | Bin 0 -> 94338 bytes reference/track/nmea_utf16_dos | Bin 0 -> 96126 bytes testo.d/gbfile.test | 15 ++++ testo.d/grapheme.test | 6 ++ util.cc | 22 ++++++ 16 files changed, 170 insertions(+), 56 deletions(-) rename cet.cc => deprecated/cet.cc (100%) rename cet.h => deprecated/cet.h (100%) create mode 100644 reference/grapheme.csv create mode 100644 reference/grapheme.gpx create mode 100644 reference/testsupplementalplane.csv create mode 100644 reference/testsupplementalplane.pcx create mode 100644 reference/track/nmea_utf16 create mode 100644 reference/track/nmea_utf16_dos create mode 100644 testo.d/gbfile.test create mode 100644 testo.d/grapheme.test diff --git a/CMakeLists.txt b/CMakeLists.txt index dc3656fca..8b251b63e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,7 +93,7 @@ set(JEEPS set(SUPPORT route.cc waypt.cc filter_vecs.cc util.cc vecs.cc mkshort.cc csv_util.cc strptime.c grtcirc.cc util_crc.cc xmlgeneric.cc - formspec.cc xmltag.cc cet.cc cet_util.cc fatal.cc rgbcolors.cc + formspec.cc xmltag.cc cet_util.cc fatal.cc rgbcolors.cc inifile.cc garmin_fs.cc units.cc gbser.cc gbfile.cc parse.cc session.cc main.cc globals.cc src/core/nvector.cc @@ -107,7 +107,6 @@ if(${QT_VERSION_MAJOR} EQUAL "6") endif() set(HEADERS - cet.h cet_util.h csv_util.h defs.h diff --git a/GPSBabel.pro b/GPSBabel.pro index e2f3a848c..732babf80 100644 --- a/GPSBabel.pro +++ b/GPSBabel.pro @@ -104,7 +104,7 @@ JEEPS += jeeps/gpsapp.cc jeeps/gpscom.cc \ SUPPORT = route.cc waypt.cc filter_vecs.cc util.cc vecs.cc mkshort.cc \ csv_util.cc strptime.c grtcirc.cc util_crc.cc xmlgeneric.cc \ - formspec.cc xmltag.cc cet.cc cet_util.cc fatal.cc rgbcolors.cc \ + formspec.cc xmltag.cc cet_util.cc fatal.cc rgbcolors.cc \ inifile.cc garmin_fs.cc units.cc gbser.cc \ gbfile.cc parse.cc session.cc main.cc globals.cc \ src/core/nvector.cc \ @@ -116,7 +116,6 @@ SUPPORT = route.cc waypt.cc filter_vecs.cc util.cc vecs.cc mkshort.cc \ versionAtLeast(QT_VERSION, 6.0): SUPPORT += src/core/codecdevice.cc HEADERS = \ - cet.h \ cet_util.h \ csv_util.h \ defs.h \ diff --git a/defs.h b/defs.h index a1dfa506a..7ee53fd09 100644 --- a/defs.h +++ b/defs.h @@ -1215,6 +1215,7 @@ int gb_ptr2int(const void* p); void list_codecs(); void list_timezones(); +QString grapheme_truncate(const QString& input, unsigned int count); /* * From parse.c diff --git a/cet.cc b/deprecated/cet.cc similarity index 100% rename from cet.cc rename to deprecated/cet.cc diff --git a/cet.h b/deprecated/cet.h similarity index 100% rename from cet.h rename to deprecated/cet.h diff --git a/gbfile.cc b/gbfile.cc index fe056dcd9..cba62e281 100644 --- a/gbfile.cc +++ b/gbfile.cc @@ -21,6 +21,7 @@ */ #include // for QByteArray +#include // for QChar, operator==, operator!= #include // for QString #include // for qPrintable @@ -34,8 +35,6 @@ #include "gbfile.h" #include "src/core/logging.h" -#include "cet.h" // for cet_ucs4_to_utf8 - #if __WIN32__ /* taken from minigzip.c (part of the zlib project) */ # include @@ -1046,65 +1045,100 @@ gbfgetpstr(gbfile* file) return QString(ba); } -static char* -gbfgetucs2str(gbfile* file) +static QChar +gbfgetutf16char(gbfile* file) { - int len = 0; - char* result = file->buff; - - for (;;) { - char buff[8]; - int c0 = gbfgetc(file); - if ((c0 == EOF) && (len == 0)) { - return nullptr; + if (c0 == EOF) { + return QChar(); } + int c1 = gbfgetc(file); - if ((c1 == EOF) && (len == 0)) { - return nullptr; + + if (c1 == EOF) { + fatal("%s: Incomplete unicode (UTF-16%cE) character at EOF!\n", + file->module, + file->big_endian ? 'B' : 'L'); } + unsigned char cell; + unsigned char row; if (file->big_endian) { - c0 = c1 | (c0 << 8); + cell = static_cast(c1); + row = static_cast(c0); } else { - c0 = c0 | (c1 << 8); + cell = static_cast(c0); + row = static_cast(c1); } + return QChar(cell, row); +} - if (c0 == '\r') { +/* + * Reads a string from utf16 encoded file. + * Terminates at EOF or a line ending(\r\n, \n). + * Line endings are not included in the returned string. + * Returns a nullptr if at EOF, otherwise it returns a pointer + * to a possibly empty null terminated utf-8 character array. + * Fatal errors can occur if: + * i) the file ends with either an incomplete utf-16 character, or + * ii) the file ends with an incomplete surrogate pair, or + * iii) a high surrogate is not followd by a low surrogate, or + * iv) a low surrogate isn't preceeded by a high surrogate. + */ +static char* +gbfgetutf16str(gbfile* file) +{ + int len = 0; + char* result = file->buff; - c0 = gbfgetc(file); - if ((c0 == EOF) && (len == 0)) { - return nullptr; - } - c1 = gbfgetc(file); - if ((c1 == EOF) && (len == 0)) { + for (;;) { + QChar qch = gbfgetutf16char(file); + if (qch.isNull()) { + if (len == 0) { return nullptr; - } - - if (file->big_endian) { - c0 = c1 | (c0 << 8); } else { - c0 = c0 | (c1 << 8); + break; } + } - if (c0 != '\n') - fatal("%s: Invalid unicode (UCS-2/%s endian) line break!\n", + if (qch == u'\r') { + QChar qch2 = gbfgetutf16char(file); + if (qch2 != u'\n') { // including qch2.isNull() + // Putting back two chars may not be supported, e.g. with gzapi_ungetc. + fatal("%s: Invalid unicode (UTF-16%cE) line break!\n", file->module, - file->big_endian ? "Big" : "Little"); + file->big_endian ? 'B' : 'L'); + } + break; + } else if (qch == u'\n') { break; } - int clen = cet_ucs4_to_utf8(buff, sizeof(buff), c0); - if (clen < 1) { - Warning() << "Malformed UCS character" << c0 << "found."; - return nullptr; + if (qch.isLowSurrogate()) { + fatal("%s: Leading unicode (UTF-16%cE) low surrogate!\n", + file->module, + file->big_endian ? 'B' : 'L'); } + QString str(qch); + if (qch.isHighSurrogate()) { + QChar qch2 = gbfgetutf16char(file); + if (!qch2.isLowSurrogate()) { // including qch2.isNull() + fatal("%s: Missing unicode (UTF-16%cE) low surrogate!\n", + file->module, + file->big_endian ? 'B' : 'L'); + } + str.append(qch2); + } + + QByteArray ba = str.toUtf8(); + int clen = ba.size(); + if (len+clen >= file->buffsz) { file->buffsz += 64; result = file->buff = (char*) xrealloc(file->buff, file->buffsz + 1); } - memcpy(&result[len], buff, clen); + memcpy(&result[len], ba.constData(), clen); len += clen; } result[len] = '\0'; // terminate resulting string @@ -1124,7 +1158,7 @@ gbfgetstr(gbfile* file) char* result = file->buff; if (file->unicode) { - return gbfgetucs2str(file); + return gbfgetutf16str(file); } for (;;) { @@ -1150,11 +1184,11 @@ gbfgetstr(gbfile* file) if (cx == 0xFEFF) { file->unicode = 1; file->big_endian = 0; - return gbfgetucs2str(file); + return gbfgetutf16str(file); } else if (cx == 0xFFFE) { file->unicode = 1; file->big_endian = 1; - return gbfgetucs2str(file); + return gbfgetutf16str(file); } else { gbfungetc(c1, file); } diff --git a/mkshort.cc b/mkshort.cc index 3936b813d..3e42244d8 100644 --- a/mkshort.cc +++ b/mkshort.cc @@ -19,16 +19,17 @@ */ -#include // for isspace, toupper, isdigit -#include // for sprintf, size_t -#include // for strlen, memmove, strchr, strcpy, strncmp, strcat, strncpy +#include // for isspace, toupper, isdigit +#include // for sprintf, size_t +#include // for strlen, memmove, strchr, strcpy, strncmp, strcat, strncpy -#include // for QList -#include // for QString -#include // for foreach +#include // for QByteArray +#include // for QChar, QChar::ReplacementCharacter +#include // for QList +#include // for QString +#include // for foreach #include "defs.h" -#include "cet.h" // for cet_utf8_strdup, cet_utf8_strlen, cet_utf8_strndup #define MYNAME "mkshort" @@ -368,7 +369,12 @@ mkshort(short_handle h, const char* istring, bool is_utf8) auto* hdl = (mkshort_handle_imp*) h; if (is_utf8) { - ostring = cet_utf8_strdup(istring); /* clean UTF-8 string */ + /* clean UTF-8 string */ + QString result = QString::fromUtf8(istring); + // QString::fromUtf8() doesn't quite promise to use QChar::ReplacementCharacter, + // but if it did toss them. + result.remove(QChar::ReplacementCharacter); + ostring = xstrdup(result.toUtf8().constData()); } else { ostring = xstrdup(istring); } @@ -518,11 +524,9 @@ mkshort(short_handle h, const char* istring, bool is_utf8) */ if (is_utf8) { /* ToDo: Keep trailing numeric data as described above! */ - if (cet_utf8_strlen(ostring) > hdl->target_len) { - char* tmp = cet_utf8_strndup(ostring, hdl->target_len); - xfree(ostring); - ostring = tmp; - } + QString result = grapheme_truncate(QString::fromUtf8(ostring), hdl->target_len); + xfree(ostring); + ostring = xstrdup(result.toUtf8().constData()); } else if ((/*i = */strlen(ostring)) > hdl->target_len) { char* dp = &ostring[hdl->target_len] - nlen; if (dp < ostring) { diff --git a/reference/grapheme.csv b/reference/grapheme.csv new file mode 100644 index 000000000..638cde873 --- /dev/null +++ b/reference/grapheme.csv @@ -0,0 +1,4 @@ +description,lat,lon +Würzburg,49.7913,9.9534 +Margetshöchheim,49.8351,9.8641 +ÿEingen,48.9464,8.6694 diff --git a/reference/grapheme.gpx b/reference/grapheme.gpx new file mode 100644 index 000000000..c565f279b --- /dev/null +++ b/reference/grapheme.gpx @@ -0,0 +1,20 @@ + + + + + + Würzbr + Würzburg + Würzburg + + + Margts + Margetshöchheim + Margetshöchheim + + + Eingen + �Eingen + �Eingen + + diff --git a/reference/testsupplementalplane.csv b/reference/testsupplementalplane.csv new file mode 100644 index 000000000..c56f44dc5 --- /dev/null +++ b/reference/testsupplementalplane.csv @@ -0,0 +1,10 @@ +No,Latitude,Longitude,Name,Description,Symbol +1,35.972033,-87.134700,"GCEBB","Mountain Bike Heaven by susy1313","Waypoint" +2,36.090683,-86.679550,"GC1A37","The 😁 Troll by a182pilot & Family","Waypoint" +3,35.996267,-86.620117,"GC1C2B","Dive Bomber by JoGPS & family","Waypoint" +4,36.038483,-86.648617,"GC25A9","FOSTER by JoGPS & Family","Waypoint" +5,36.112183,-86.741767,"GC2723","Logan Lighthouse by JoGps & Family","Waypoint" +6,36.064083,-86.790517,"GC2B71","Ganier Cache by Susy1313","Waypoint" +7,36.087767,-86.809733,"GC309F","Shy's Hill by FireFighterEng33","Waypoint" +8,36.057500,-86.892000,"GC317A","GittyUp by JoGPS / Warner Parks","Waypoint" +9,36.082800,-86.867283,"GC317D","Inlighting by JoGPS / Warner Parks","Waypoint" diff --git a/reference/testsupplementalplane.pcx b/reference/testsupplementalplane.pcx new file mode 100644 index 0000000000000000000000000000000000000000..5cadadddb44b5ada5fc09e9c2cb12b517d5714d8 GIT binary patch literal 1986 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 V|4dk|Mq~+(nSCbg%=bg&{{eNf3(5ch literal 0 HcmV?d00001 diff --git a/reference/track/nmea_utf16_dos b/reference/track/nmea_utf16_dos new file mode 100644 index 0000000000000000000000000000000000000000..8b89440af34fd60e6ff3acf0815209bbaef94c97 GIT binary patch literal 96126 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 Njo!6)R52Kz{|9r+IWPbK literal 0 HcmV?d00001 diff --git a/testo.d/gbfile.test b/testo.d/gbfile.test new file mode 100644 index 000000000..cd59a6a09 --- /dev/null +++ b/testo.d/gbfile.test @@ -0,0 +1,15 @@ +# these are here to test gbfile utf16 reads. + +# Assumes nmea reader is still using gbgetstr. +# These are handcrafted input files, they may not be legal nmea files. +# with \d line endings +gpsbabel -i nmea -f ${REFERENCE}/track/nmea_utf16 -o gpx -F ${TMPDIR}/nmea_utf16.gpx +compare ${REFERENCE}/track/nmea.gpx ${TMPDIR}/nmea_utf16.gpx +# with \r\n line endings +gpsbabel -i nmea -f ${REFERENCE}/track/nmea_utf16_dos -o gpx -F ${TMPDIR}/nmea_utf16_dos.gpx +compare ${REFERENCE}/track/nmea.gpx ${TMPDIR}/nmea_utf16_dos.gpx +# Assumes pcx reader is still using gbgetstr. +# These are handcrafted input files, they may not be legal pcx files. +# with a unicode character from the supplemental plane encoded in utf16le. +gpsbabel -i pcx -f ${REFERENCE}/testsupplementalplane.pcx -o unicsv -F ${TMPDIR}/testsupplementalplane.csv +compare ${REFERENCE}/testsupplementalplane.csv ${TMPDIR}/testsupplementalplane.csv diff --git a/testo.d/grapheme.test b/testo.d/grapheme.test new file mode 100644 index 000000000..0ac5a5439 --- /dev/null +++ b/testo.d/grapheme.test @@ -0,0 +1,6 @@ +# test mkshort utf8 to see it breaks on grapheme boundaries +# and tosses invalid sequences. +# note grapheme.csv uses the combining diacritical mark U+0308. +# note grapheme.csv has an invalid byte 0xff. +gpsbabel -s -i unicsv -f ${REFERENCE}/grapheme.csv -o gpx,snlen=6 -F ${TMPDIR}/grapheme.gpx +compare ${REFERENCE}/grapheme.gpx ${TMPDIR}/grapheme.gpx diff --git a/util.cc b/util.cc index fb6c83b82..7db6af8a8 100644 --- a/util.cc +++ b/util.cc @@ -37,6 +37,7 @@ #include // for QList #include // for QScopedPointer #include // for QString +#include // for QTextBoundaryFinder, QTextBoundaryFinder::Grapheme #include // for QTextCodec #include // for operator<<, QTextStream, qSetFieldWidth, endl, QTextStream::AlignLeft #include // for QXmlStreamAttribute @@ -1732,3 +1733,24 @@ void list_timezones() } } +QString grapheme_truncate(const QString& input, unsigned int count) +{ + QString output(input); + QTextBoundaryFinder boundary(QTextBoundaryFinder::Grapheme, input); + boundary.toStart(); + unsigned int grapheme_cnt = 0; + QList boundaries{0}; + while (boundary.toNextBoundary() >= 0) { + ++grapheme_cnt; + boundaries.append(boundary.position()); + } + if (grapheme_cnt > count) { + output.truncate(boundaries.at(count)); + } + if constexpr(false) { + qDebug() << input << "->" << output << boundaries << ", limit:" << + count << ", input QChars:" << input.size() << ",input graphemes:" << grapheme_cnt << + ", output QChars:" << output.size(); + } + return output; +} -- 2.30.2